package com.alex.statistics.method.spatialAnalysis;

import com.alex.statistics.pojo.request.spatialAnalysis.LocalLisaRequest;
import com.alex.statistics.pojo.result.spatialAnalysis.LocalLisaResult;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.geotools.geojson.feature.FeatureJSON;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.StringReader;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Service
public class LocalLisaService {

    // 聚类类型枚举（替代字符串常量，增强类型安全）
    public enum ClusterType {
        HH("高-高聚类"), HL("高-低异常"), LH("低-高异常"), LL("低-低聚类"), NS("不显著");

        private final String description;
        ClusterType(String description) {
            this.description = description;
        }
        public String getDescription() {
            return description;
        }
    }

    private final FeatureJSON featureJson = new FeatureJSON();

    public LocalLisaResult calculate(LocalLisaRequest request) {
        long startTime = System.currentTimeMillis();
        validateInput(request);

        // 1. 解析GeoJSON并预处理（关键：提前缓存要素ID和几何中心，确保顺序）
        List<SimpleFeature> features = parseGeoJsonFeatures(request.getGeoJson());
        int featureCount = features.size();

        // 缓存要素ID列表（与GeoJSON顺序严格一致）
        List<String> featureIds = features.stream()
                .map(SimpleFeature::getID)
                .collect(Collectors.toList());

        // 缓存要素数值列表（与GeoJSON顺序严格一致）
        List<Double> values = extractValuesFromFeatures(features, request.getValueField());

        // 缓存要素几何中心（用于权重计算，与GeoJSON顺序严格一致）
        List<Point> centroids = features.stream()
                .map(feature -> ((Geometry) feature.getDefaultGeometry()).getCentroid())
                .collect(Collectors.toList());

        // 2. 生成空间权重矩阵
        double[][] weights = generateSpatialWeights(features, centroids, request);
        double mean = values.stream().mapToDouble(Double::doubleValue).average()
                .orElseThrow(() -> new IllegalArgumentException("无法计算均值：输入要素为空"));

        // 3. 并行计算Local LISA指标（使用有序列表存储结果，保证顺序）
        // 使用CopyOnWriteArrayList确保线程安全且顺序一致
        List<ClusterType> clusterTypes = new CopyOnWriteArrayList<>(new ClusterType[featureCount]);
        List<Double> localMoranValues = new CopyOnWriteArrayList<>(new Double[featureCount]);
        List<Double> pValues = new CopyOnWriteArrayList<>(new Double[featureCount]);

        // 按索引并行计算，直接写入对应位置，天然保证顺序
        IntStream.range(0, featureCount).parallel().forEach(index -> {
            try {
                double localI = computeLocalMoranI(index, values, weights, mean);
                double pValue = computePValue(index, values, weights, localI, request.getPermutations());
                ClusterType type = determineClusterType(
                        index, values, weights, localI, pValue, request.getSignificanceLevel(), mean
                );

                // 直接写入对应索引位置，与GeoJSON顺序严格一致
                clusterTypes.set(index, type);
                localMoranValues.set(index, localI);
                pValues.set(index, pValue);
            } catch (Exception e) {
                // 异常时记录详细信息（包含featureId），便于排查
                String featureId = featureIds.get(index);
                System.err.printf("处理要素[%s]（索引%d）时出错：%s%n", featureId, index, e.getMessage());

                // 异常值处理，仍保持顺序
                clusterTypes.set(index, ClusterType.NS);
                localMoranValues.set(index, 0.0);
                pValues.set(index, Double.NaN);
            }
        });

        // 4. 构建结果（直接使用有序列表，无需额外排序）
        LocalLisaResult result = new LocalLisaResult();
        result.setClusterTypes(clusterTypes.stream()
                .map(ClusterType::name)  // 转为字符串便于序列化
                .collect(Collectors.toList()));
        result.setLocalMoranValues(localMoranValues);
        result.setPValues(pValues);
        result.setFeatureIds(featureIds);
        result.setSignificanceLevel(request.getSignificanceLevel());
        result.setComputeTime(System.currentTimeMillis() - startTime);
//        result.setFeatureCount(featureCount);

        return result;
    }

    // ------------------------------
    // 核心方法实现（优化部分）
    // ------------------------------

    /**
     * 解析GeoJSON要素，确保顺序与输入一致
     */
    private List<SimpleFeature> parseGeoJsonFeatures(String geoJson) {
        try {
            List<SimpleFeature> features = new ArrayList<>();
            var featureCollection = featureJson.readFeatureCollection(new StringReader(geoJson));
            try (var iterator = featureCollection.features()) {
                while (iterator.hasNext()) {
                    features.add((SimpleFeature) iterator.next());
                }
            }
            if (features.isEmpty()) {
                throw new IllegalArgumentException("GeoJSON中未包含任何要素");
            }
            return features;
        } catch (Exception e) {
            throw new IllegalArgumentException("GeoJSON解析失败：" + e.getMessage(), e);
        }
    }

    /**
     * 提取数值并保持与GeoJSON要素顺序一致
     */
    private List<Double> extractValuesFromFeatures(List<SimpleFeature> features, String valueField) {
        return features.stream()
                .map(feature -> {
                    Property property = feature.getProperty(valueField);
                    if (property == null) {
                        throw new IllegalArgumentException(
                                String.format("要素[%s]中未找到属性字段：%s", feature.getID(), valueField)
                        );
                    }
                    Object value = property.getValue();
                    if (value instanceof Number) {
                        return ((Number) value).doubleValue();
                    } else {
                        throw new IllegalArgumentException(
                                String.format("要素[%s]的属性字段[%s]不是数值类型（类型：%s）",
                                        feature.getID(), valueField, value.getClass().getSimpleName())
                        );
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * 生成空间权重矩阵（使用预缓存的centroids提高效率）
     */
    private double[][] generateSpatialWeights(List<SimpleFeature> features, List<Point> centroids, LocalLisaRequest request) {
        int n = features.size();
        switch (request.getWeightType()) {
            case KNN:
                return generateKnnWeights(centroids, request.getKnnK());
            case DISTANCE_THRESHOLD:
                return generateDistanceThresholdWeights(centroids, request.getDistanceThreshold());
            case QUEEN:
                return generateQueenWeights(features);
            case ROOK:
                return generateRookWeights(features);
            default:
                throw new IllegalArgumentException("不支持的权重类型：" + request.getWeightType());
        }
    }

    /**
     * 计算局部Moran's I值
     */
    private double computeLocalMoranI(int index, List<Double> values, double[][] weights, double mean) {
        double zi = values.get(index) - mean;
        double sum = 0;
        double weightSum = 0;

        for (int j = 0; j < values.size(); j++) {
            if (index != j) {
                double zj = values.get(j) - mean;
                sum += weights[index][j] * zi * zj;
                weightSum += weights[index][j];
            }
        }

        return weightSum != 0 ? sum / weightSum : 0;
    }

    /**
     * 蒙特卡洛置换检验计算P值
     */
    private double computePValue(int index, List<Double> values, double[][] weights,
                                 double observedI, int permutations) {
        if (permutations <= 0) {
            return Double.NaN;
        }

        int countExtreme = 0;
        Random random = new Random(index);  // 使用index作为种子，保证结果可复现
        double mean = values.stream().mapToDouble(Double::doubleValue).average().orElse(0);

        for (int p = 0; p < permutations; p++) {
            List<Double> shuffled = new ArrayList<>(values);
            List<Double> neighbors = new ArrayList<>();
            for (int j = 0; j < shuffled.size(); j++) {
                if (j != index) {
                    neighbors.add(shuffled.get(j));
                }
            }
            Collections.shuffle(neighbors, random);

            int neighborIdx = 0;
            for (int j = 0; j < shuffled.size(); j++) {
                if (j != index) {
                    shuffled.set(j, neighbors.get(neighborIdx++));
                }
            }

            double randomI = computeLocalMoranI(index, shuffled, weights, mean);
            if (Math.abs(randomI) >= Math.abs(observedI)) {
                countExtreme++;
            }
        }

        return (countExtreme + 1.0) / (permutations + 1.0);
    }

    /**
     * 判断聚类类型
     */
    private ClusterType determineClusterType(int index, List<Double> values, double[][] weights,
                                             double localI, double pValue, double alpha, double mean) {
        if (pValue > alpha || Double.isNaN(pValue)) {
            return ClusterType.NS;
        }

        double zi = values.get(index) - mean;
        double neighborMean = 0;
        double weightSum = 0;

        for (int j = 0; j < values.size(); j++) {
            if (index != j) {
                neighborMean += weights[index][j] * values.get(j);
                weightSum += weights[index][j];
            }
        }

        double zNeighbor = (weightSum != 0 ? neighborMean / weightSum : 0) - mean;

        if (zi > 0 && zNeighbor > 0) return ClusterType.HH;
        if (zi > 0 && zNeighbor < 0) return ClusterType.HL;
        if (zi < 0 && zNeighbor > 0) return ClusterType.LH;
        if (zi < 0 && zNeighbor < 0) return ClusterType.LL;
        return ClusterType.NS;
    }

    /**
     * 参数校验
     */
    private void validateInput(LocalLisaRequest request) {
        if (request.getGeoJson() == null || request.getGeoJson().trim().isEmpty()) {
            throw new IllegalArgumentException("GeoJSON不能为空");
        }
        if (!StringUtils.hasText(request.getValueField())) {
            throw new IllegalArgumentException("必须指定valueField（属性字段名）");
        }
        if (request.getWeightType() == null) {
            throw new IllegalArgumentException("必须指定weightType（权重矩阵类型）");
        }

        // 权重类型参数校验
        if (request.getWeightType() == LocalLisaRequest.WeightType.KNN) {
            if (request.getKnnK() == null || request.getKnnK() <= 0 || request.getKnnK() >= 100) {
                throw new IllegalArgumentException("KNN的knnK必须为1-99之间的整数");
            }
        } else if (request.getWeightType() == LocalLisaRequest.WeightType.DISTANCE_THRESHOLD) {
            if (request.getDistanceThreshold() == null || request.getDistanceThreshold() <= 0) {
                throw new IllegalArgumentException("距离阈值必须为正数");
            }
        }

        // 显著性检验参数校验
        if (request.getSignificanceLevel() <= 0 || request.getSignificanceLevel() >= 1) {
            throw new IllegalArgumentException("显著性水平必须在(0,1)区间内（推荐0.05）");
        }
        if (request.getPermutations() < 0 || request.getPermutations() > 10000) {
            throw new IllegalArgumentException("置换次数必须为0-10000之间（推荐999或1999）");
        }
    }

    // ------------------------------
    // 权重矩阵生成方法（保持原有逻辑）
    // ------------------------------

    private double[][] generateKnnWeights(List<Point> centroids, int k) {
        int n = centroids.size();
        double[][] weights = new double[n][n];
        for (int i = 0; i < n; i++) {
            Point pointI = centroids.get(i);
            Map<Integer, Double> distanceMap = new HashMap<>();
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    distanceMap.put(j, pointI.distance(centroids.get(j)));
                }
            }
            List<Integer> nearestNeighbors = distanceMap.entrySet().stream()
                    .sorted(Map.Entry.comparingByValue())
                    .limit(k)
                    .map(Map.Entry::getKey)
                    .collect(Collectors.toList());
            double weight = 1.0 / k;
            for (int j : nearestNeighbors) {
                weights[i][j] = weight;
            }
        }
        return weights;
    }

    private double[][] generateDistanceThresholdWeights(List<Point> centroids, double threshold) {
        int n = centroids.size();
        double[][] weights = new double[n][n];
        for (int i = 0; i < n; i++) {
            Point pointI = centroids.get(i);
            List<Integer> neighbors = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if (i != j && pointI.distance(centroids.get(j)) <= threshold) {
                    neighbors.add(j);
                }
            }
            if (!neighbors.isEmpty()) {
                double weight = 1.0 / neighbors.size();
                for (int j : neighbors) {
                    weights[i][j] = weight;
                }
            }
        }
        return weights;
    }

    private double[][] generateQueenWeights(List<SimpleFeature> features) {
        int n = features.size();
        double[][] weights = new double[n][n];
        for (int i = 0; i < n; i++) {
            Geometry geomI = (Geometry) features.get(i).getDefaultGeometry();
            List<Integer> neighbors = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    Geometry geomJ = (Geometry) features.get(j).getDefaultGeometry();
                    if (geomI.touches(geomJ) || geomI.intersection(geomJ).getDimension() >= 0) {
                        neighbors.add(j);
                    }
                }
            }
            if (!neighbors.isEmpty()) {
                double weight = 1.0 / neighbors.size();
                for (int j : neighbors) {
                    weights[i][j] = weight;
                }
            }
        }
        return weights;
    }

    private double[][] generateRookWeights(List<SimpleFeature> features) {
        int n = features.size();
        double[][] weights = new double[n][n];
        for (int i = 0; i < n; i++) {
            Geometry geomI = (Geometry) features.get(i).getDefaultGeometry();
            List<Integer> neighbors = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    Geometry geomJ = (Geometry) features.get(j).getDefaultGeometry();
                    Geometry intersection = geomI.intersection(geomJ);
                    if (intersection.getDimension() == 1) {
                        neighbors.add(j);
                    }
                }
            }
            if (!neighbors.isEmpty()) {
                double weight = 1.0 / neighbors.size();
                for (int j : neighbors) {
                    weights[i][j] = weight;
                }
            }
        }
        return weights;
    }
}
